﻿using System.Reflection;
using Core;
using Core.Infrastructure;
using Core.Plugins;
using Microsoft.AspNetCore.Mvc.ApplicationParts;
using Microsoft.Extensions.DependencyInjection;

namespace Infrastructure.Extensions;

#nullable disable

/// <summary>
/// Represents application part manager extensions
/// </summary>
public static partial class ApplicationPartManagerExtensions
{
    #region Fields

    private static readonly IAKitFileProvider _fileProvider;
    private static readonly List<KeyValuePair<string, Version>> _baseAppLibraries;
    private static readonly List<KeyValuePair<string, Version>> _pluginLibraries;

    #endregion

    #region Ctor

    static ApplicationPartManagerExtensions()
    {
        //we use the default file provider, since the DI isn't initialized yet
        _fileProvider = CommonHelper.DefaultFileProvider;

        _baseAppLibraries = new List<KeyValuePair<string, Version>>();
        _pluginLibraries = new List<KeyValuePair<string, Version>>();

        //get all libraries from /bin/{version}/ directory
        foreach (var file in _fileProvider.GetFiles(AppDomain.CurrentDomain.BaseDirectory, "*.dll"))
            _baseAppLibraries.Add(new KeyValuePair<string, Version>(file, GetAssemblyVersion(file)));

        //get all libraries from base site directory
        if (!AppDomain.CurrentDomain.BaseDirectory.Equals(Environment.CurrentDirectory, StringComparison.InvariantCultureIgnoreCase))
            foreach (var file in _fileProvider.GetFiles(Environment.CurrentDirectory, "*.dll"))
                _baseAppLibraries.Add(new KeyValuePair<string, Version>(file, GetAssemblyVersion(file)));

        //get all libraries from refs directory
        var refsPathName = _fileProvider.Combine(Environment.CurrentDirectory, AKitPluginDefaults.RefsPathName);
        if (_fileProvider.DirectoryExists(refsPathName))
            foreach (var file in _fileProvider.GetFiles(refsPathName, "*.dll"))
                _baseAppLibraries.Add(new KeyValuePair<string, Version>(file, GetAssemblyVersion(file)));
    }

    #endregion

    #region Properties

    #endregion

    #region Utilities

    private static Version GetAssemblyVersion(string filePath)
    {
        try
        {
            return AssemblyName.GetAssemblyName(filePath).Version;
        }
        catch (BadImageFormatException)
        {
            //ignore
        }

        return null;
    }

    /// <summary>
    /// Load and register the assembly
    /// </summary>
    /// <param name="applicationPartManager">Application part manager</param>
    /// <param name="assemblyFile">Path to the assembly file</param>
    /// <param name="useUnsafeLoadAssembly">Indicating whether to load an assembly into the load-from context, bypassing some security checks</param>
    /// <returns>Assembly</returns>
    private static Assembly AddApplicationParts(ApplicationPartManager applicationPartManager, string assemblyFile, bool useUnsafeLoadAssembly)
    {
        //try to load a assembly
        Assembly assembly;

        try
        {
            assembly = Assembly.LoadFrom(assemblyFile);
        }
        catch (FileLoadException)
        {
            if (useUnsafeLoadAssembly)
            {
                //if an application has been copied from the web, it is flagged by Windows as being a web application,
                //even if it resides on the local computer.You can change that designation by changing the file properties,
                //or you can use the<loadFromRemoteSources> element to grant the assembly full trust.As an alternative,
                //you can use the UnsafeLoadFrom method to load a local assembly that the operating system has flagged as
                //having been loaded from the web.
                //see http://go.microsoft.com/fwlink/?LinkId=155569 for more information.
                assembly = Assembly.UnsafeLoadFrom(assemblyFile);
            }
            else
                throw;
        }

        //register the plugin definition
        applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly));

        return assembly;
    }

    /// <summary>
    /// Check whether the assembly is already loaded
    /// </summary>
    /// <param name="filePath">Assembly file path</param>
    /// <param name="pluginName">Plugin system name</param>
    /// <returns>Result of check</returns>
    private static bool IsAlreadyLoaded(string filePath, string pluginName)
    {
        //ignore already loaded libraries
        //(we do it because not all libraries are loaded immediately after application start)
        var fileName = _fileProvider.GetFileName(filePath);
        if (_baseAppLibraries.Any(library => library.Key.Equals(fileName, StringComparison.InvariantCultureIgnoreCase)))
            return true;

        try
        {
            //get filename without extension
            var fileNameWithoutExtension = _fileProvider.GetFileNameWithoutExtension(filePath);
            if (string.IsNullOrEmpty(fileNameWithoutExtension))
                throw new Exception($"Cannot get file extension for {fileName}");

            foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
            {
                //compare assemblies by file names
                var assemblyName = (assembly.FullName ?? string.Empty).Split(',').FirstOrDefault();
                if (!fileNameWithoutExtension.Equals(assemblyName, StringComparison.InvariantCultureIgnoreCase))
                    continue;

                return true;
            }
        }
        catch
        {
            // ignored
        }

        //nothing found
        return false;
    }

    #endregion

    #region Methods

    /// <summary>
    /// Initialize plugins system
    /// </summary>
    /// <param name="applicationPartManager">Application part manager</param>
    /// <param name="services"></param>
    public static void InitializePlugins(this ApplicationPartManager applicationPartManager, IServiceCollection services)
    {
        ArgumentNullException.ThrowIfNull(applicationPartManager);
        _pluginLibraries.AddRange(_baseAppLibraries);
        foreach (var item in _pluginLibraries)
        {
            Assembly assembly = Assembly.LoadFrom(item.Key);

            applicationPartManager.ApplicationParts.Add(new AssemblyPart(assembly));

            if (assembly.GetName().Name.StartsWith("AKit", StringComparison.OrdinalIgnoreCase))
                services.AddMediatR(config =>
                {
                    config.RegisterServicesFromAssembly(assembly);
                });
        }
    }

    #endregion
}
